/**
 * Copyright (c) 快宝网络 kuaidihelp.com Co., Ltd. All Rights Reserved 禁止外泄以及用于其它的商业用途
 */

import CryptoJS from 'crypto-js';
import { iv } from './user';
import requestCrypto from './request-crypto';
import { utils } from '@base/config';
import qs from 'qs';
import { RequestOptionsInit } from 'umi-request';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { debounce } from 'lodash';

interface EncryptionParams {
  [key: string]: any;
}

type GetKeys = (p: any) => Promise<any>;

/**
 * CryptoJS 加密
 * @param {params} params 需要加密的对象
 */
export async function encryption(params: EncryptionParams, getKeys: GetKeys, encode = false) {
  if (!getKeys) {
    console.error('请传入GetKeys，即原：service.loginEncryption');
  }
  const res = getKeys ? await getKeys(params) : {};
  const { code, data = {} } = res;
  const { token, key: sKey } = data;

  const cryptoKeys = Object.keys(data)
    .filter((i) => i !== 'token')
    .sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0));

  const cryptoKey = sKey || cryptoKeys.map((i) => data[i]).join('');

  const cipher = (value: string, key: string) =>
    iv
      ? CryptoJS.AES.encrypt(value, CryptoJS.enc.Utf8.parse(key), {
          iv: CryptoJS.enc.Utf8.parse(iv),
          padding: CryptoJS.pad.Pkcs7,
          mode: CryptoJS.mode.CBC,
        })
      : value;

  if (code == 0 && token) {
    const result: any = {
      token,
    };
    Object.keys(params).forEach((j) => {
      const value = params[j];
      const cipherValue = cipher(value, cryptoKey).toString();
      result[j] = encode ? encodeURIComponent(cipherValue) : cipherValue;
    });

    return {
      code: 0,
      result,
    };
  } else {
    return res;
  }
}

/**
 *
 * @description 创建nonce
 * @param {string} key
 * @param {any} data
 * @returns {string}
 */
function createNonceData<T>(key?: string, data?: T) {
  if (key && data) {
    const arr = key.split(',');
    const nonce = arr
      .map((item) => data[item])
      .filter((item) => !!item)
      .join('');
    return nonce;
  }
  return '';
}

export interface CryptoParams {
  url: string;
  data?: any;
  nonceKey?: string;
  options?: RequestOptionsInit & { nonceKey?: string };
}

/**
 *
 * @description 加密请求地址
 * @param  {CryptoParams} params
 * @returns
 */
export function crypto(params: CryptoParams): Promise<string> {
  return new Promise((resolve) => {
    let { url } = params;
    url = url.trim();
    const { appId: app_id } = utils.crypto || {};
    if (!app_id || !url) {
      resolve(url);
      return;
    }
    const { options } = params;
    const { nonceKey, data, params: optsParams } = options || {};
    const nonceData = createNonceData(nonceKey, {
      ...(typeof data === 'string' ? qs.parse(data) : data),
      ...optsParams,
    });
    const ready = requestCrypto();
    ready.then((ins: any) => {
      const sessionId = window.sessionId || '';
      const [url_] = url.split('?');
      const [sign, nonce, ts] = ins.KbCryptoHelper.buildDigest(url_, app_id, sessionId, nonceData);
      resolve(
        url +
          `${url.includes('?') ? '&' : '?'}${qs.stringify({
            sign_n: sign,
            nonce_n: nonce,
            ts_n: ts,
            app_id_n: app_id,
          })}`,
      );
    });
  });
}

/**
 *
 * @description 加密请求
 * @param url
 * @returns
 */

interface CryptoResult {
  uri: string;
  loading: boolean;
  update: (u?: string) => void;
}

interface CryptoOptions {
  uri: string;
  nonceKey?: string;
  data?: { [k: string]: any };
}

export function useCrypto(opts: CryptoOptions, deps: string[] = []): CryptoResult {
  const { uri: optsUri, data = null, nonceKey } = opts;
  const [uriWithSign, setUriWithSign] = useState('');
  const [loading, setLoading] = useState(false);

  const update = useCallback(
    debounce(
      (u: string = optsUri) => {
        setLoading(true);
        return crypto({
          url: u,
          options: {
            nonceKey,
            data,
          },
        })
          .then(setUriWithSign)
          .finally(() => setLoading(false));
      },
      300,
      { trailing: true, leading: false },
    ),
    [optsUri],
  );

  useEffect(() => {
    update(optsUri);
  }, [optsUri, ...deps]);

  const uri = useMemo(() => {
    if (!data) return uriWithSign;
    return `${uriWithSign}&${qs.stringify(data)}`;
  }, [uriWithSign]);

  return { uri, loading, update };
}
